(in-package :lilu-lem)

;;;
;;; lem program
;;;

(defun build-lem-options (application &key file eval slime-port repl-p theme)
  (declare (ignore application))
  (flet ((get-local-lem-file (pathname)
           (asdf:system-relative-pathname 'lilu-lem (merge-pathnames pathname #p"src/"))))
    (let ((options `("--eval"
                     ,(format nil "(load \"~A\")" (namestring (get-local-lem-file "lem-server-init.lisp")))
                     ,@(when eval (list "--eval" eval))
                     ,@(when theme
                         (list "--eval" (format nil "(lem-user::load-theme \"~A\")"
                                                (if (symbolp theme)
                                                    (string-downcase (symbol-name theme))
                                                    theme))))
                     ,@(when slime-port
                         (list "--slime" (format nil "localhost:~A" slime-port)))
                     ,@(when repl-p
                         (list "--eval" "(lem-lisp-mode:start-lisp-repl)"))
                     ,@(when file
                         (list file)))))
      options)))

(defclass lem-application (lilu-app:simple-program-application-mixin
                           lilu-app:application)
  ()
  (:default-initargs
   :name (string :lem)
   :program "lem"
   :version-option "--version"
   :version-script "lem --version | awk -F '[ -]' '{print $2}'"
   :tags '(:repl :editor :debugger :inspector)
   :make-args-fn #'build-lem-options))

(defvar *lem-program* (lilu-app:register-application
                       (make-instance 'lem-application)))

(defun run-lem-program (&rest args &key file eval slime-port
                                     repl-p theme (wait-p t) (error-p t))
  (declare (ignorable file eval slime-port repl-p theme))
  (let ((cmd (apply #'lilu-app:make-simple-program-command
                    *lem-program*
                    args)))
    (lilu-terminal:open-terminal :cmd cmd :error-p error-p :wait-p wait-p)))

;;;
;;; lem
;;;

(defparameter *default-lem-theme* 'emacs-dark)

(defun open-lem (&key (theme *default-lem-theme*)
                   (wait-p nil) (error-p t))
  (multiple-value-bind (msg ret)
      (run-lem-program :theme theme :wait-p wait-p :repl-p t :error-p error-p)
    (if ret
        (values msg)
        (values nil msg))))

(defun lem-edit (file &key (theme *default-lem-theme*)
                            (wait-p t) (error-p t))
  (multiple-value-bind (msg ret)
      (run-lem-program :theme theme :file file :wait-p wait-p :error-p error-p)
    (if ret
        (values (or msg t))
        (values nil msg))))

;;;
;;; slime
;;;

(defparameter *default-lem-server-name* "lilu-lem")
(defparameter *default-lem-slime-port* 4021)

(defun open-lem-slime (&key (server-name *default-lem-server-name*)
                         (slime-port *default-lem-slime-port*)
                         (theme *default-lem-theme*)
                         (wait-p nil)
                         (error-p t))
  (if (lem-slime-open-p :server-name server-name :error-p nil)
      (progn
        (warn (format nil "slime in emacs server (~A) is already running." server-name))
        t)
      (flet ((slime-open-fn ()
               (multiple-value-bind (output ret)
                   (run-lem-program
                    :slime-port slime-port
                    :theme theme
                    :wait-p nil
                    :repl-p t
                    :error-p error-p)
                 (declare (ignore ret))
                 (when (not output)
                   (if error-p
                       (error "slime -- connetion to swank server failed")
                       (warn  "slime -- connetion to swank server failed"))))))
        (lilu-swank:open-slime-connection server-name slime-port #'slime-open-fn)
        (when wait-p
          (lilu-swank:wait-swank-connection server-name))
        t)))

(defun lem-slime-open-p (&key (server-name *default-lem-server-name*)
                           (verify-p t) (error-p nil))
  (if (lilu-swank:find-swank-connection server-name :verify-p verify-p)
      t
      (if error-p
          (error "slime is not running in the lem server (name: ~A)" server-name)
          nil)))

;;;
;;; debugger
;;;

(defun call-lem-debugger (server-name theme slime-port
                            condition me-or-my-encapsulation)
  (unless (lilu-swank:find-swank-connection server-name :verify-p t)
    (open-lem-slime :server-name server-name
                    :theme theme
                    :slime-port slime-port
                    :wait-p nil
                    :error-p t)
    (sleep 2))
  (lilu-swank:call-slime-debugger server-name condition me-or-my-encapsulation))

(defun make-lem-debugger-hook (&key (server-name lilu-lem:*default-lem-server-name*)
                                 (theme lilu-lem:*default-lem-theme*)
                                 (slime-port lilu-lem:*default-lem-slime-port*))
  (lilu-debugger:make-standard-hook (:lem condition me-or-my-encapsulation)
    (call-lem-debugger server-name theme slime-port
                       condition
                       me-or-my-encapsulation)))

(defvar *lem-debugger-hook* (make-lem-debugger-hook))

;;;
;;; lem
;;;

(defmethod lilu-app:run-open-repl ((application lem-application) &key error-p wait-p)
  (open-lem-slime :wait-p wait-p :error-p error-p))

(defmethod lilu-app:run-open-editor ((application lem-application) &key file nline error-p wait-p)
  (if file
      (lem-edit file :error-p error-p :wait-p wait-p)
      (open-lem :error-p error-p :wait-p wait-p)))

(defmethod lilu-app:get-debugger-hook ((application lem-application))
  *lem-debugger-hook*)

(defmethod lilu-app:run-open-inspector ((application lem-application) object &key error-p wait-p)
  (declare (ignore wait-p))
  (open-lem-slime :wait-p nil :error-p error-p)
  (sleep 1)
  (lilu-swank:with-swank-connection (con *default-lem-server-name*)
    (swank:inspect-in-emacs object)))
